﻿using Blm.VisionCloud.FrameWork.Cached;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Threading.Tasks;
using VisionCloud.Domain.Common;
using VisionCloud.Domain.Extension;
using VisionCloud.Domain.Filters;
using VisionCloud.Utility;
using VisionCloud.Utility.Extensions;
using static VisionCloud.Core.Model.Sys.SysUser;

namespace VisionCloud.Web.Authorize
{
    /// <summary>
    /// 自定义授权处理
    /// </summary>
    public class JwtTokenAuthorizationHandler : AuthorizationHandler<JwtTokenAuthorizationRequirement>
    {
        /// <summary>
        /// 授权方式（cookie, bearer, oauth, openid）
        /// </summary>
        private readonly IAuthenticationSchemeProvider schemes;

        /// <summary>
        /// 缓存注入
        /// </summary>
        private readonly ICached cached;

        /// <summary>
        /// 日志
        /// </summary>
        private readonly ILogger<JwtTokenAuthorizationHandler> logger;

        /// <summary>
        /// 当前http请求的访问器
        /// </summary>
        private readonly IHttpContextAccessor httpContextAccessor;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="schemes"></param>
        /// <param name="cached"></param>
        /// <param name="logger"></param>
        public JwtTokenAuthorizationHandler(ICached cached,
            IAuthenticationSchemeProvider schemes,
            ILogger<JwtTokenAuthorizationHandler> logger,
            IHttpContextAccessor httpContextAccessor)
        {
            this.cached = cached;
            this.schemes = schemes;
            this.logger = logger;
            this.httpContextAccessor = httpContextAccessor;
        }

        /// <summary>
        /// 授权处理：控制器和接口都有指定特性会访问该方法多次
        /// 1.测量机器人用户进行api路径验证
        /// 2.平台角色走对称加密验证并不进行api路径验证
        /// 3.因为原来设计时使用的是返回文字和401校验，而不是使用不同状态码，所以这里必须有返回内容
        /// </summary>
        /// <param name="context"></param>
        /// <param name="requirement"></param>
        /// <returns></returns>
        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, JwtTokenAuthorizationRequirement requirement)
        {
            var authorizationFailedContext = new AuthorizationFailedContext { Message = StartupServer.CommonLocalizer["YB0143"].Value };
            var httpContext = httpContextAccessor.HttpContext;
            var request = httpContext.Request;
            if (request.Method.Equals("OPTIONS", StringComparison.OrdinalIgnoreCase))
            {
                context.Succeed(requirement);
                return;
            }

            // 设置baseInfo
            var baseInfo = new OperatorBaseInfo()
            {
                Browser = httpContext.GetBrowser(),
                VisitPath = request.Path,
                Ip = httpContext.GetClientUserIp(),
            };

            if (context.Resource is RouteEndpoint endpoint)
            {
                var module = (SystemModuleAttribute)endpoint.Metadata.FirstOrDefault(x => x.GetType() == typeof(SystemModuleAttribute));
                baseInfo.ModuleTitle = module?.ModuleTitle;
            }

            try
            {
                var operatorInfo = await OperatorProvider.Set(request.Headers["Authorization"], baseInfo);
                if (operatorInfo == null)
                {
                    AuthFailSet(context, authorizationFailedContext, httpContext);
                    return;
                }
            }
            catch (Exception ex)
            {
                logger.LogInformation("HandleRequirementAsync", ex);
                AuthFailSet(context, authorizationFailedContext, httpContext);
                return;
            }

            // 获取授权方式
            AuthenticationScheme defaultAuthenticate = await schemes.GetDefaultAuthenticateSchemeAsync().ConfigureAwait(false);
            if (defaultAuthenticate == null)
            {
                AuthFailSet(context, authorizationFailedContext, httpContext, StartupServer.CommonLocalizer["YB0144"].Value);
                return;
            }
            // 验证token（包括过期时间）
            AuthenticateResult result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name).ConfigureAwait(false);
            if (!result.Succeeded)
            {
                var msg = result?.Failure != null ? StartupServer.CommonLocalizer["YB0145"].Value : StartupServer.CommonLocalizer["YB0146"].Value;
                AuthFailSet(context, authorizationFailedContext, httpContext, msg);
                return;
            }
            httpContext.User = result.Principal;

            // 判断角色
            string url = httpContext.Request.Path.Value.ToLower();
            string logId = httpContext.User.Claims.FirstOrDefault(t => t.Type.Equals("LogId", StringComparison.CurrentCulture))?.Value;
            if (string.IsNullOrEmpty(logId))
            {
                // 请重新登录:登录信息版本不正确
                AuthFailSet(context, authorizationFailedContext, httpContext, StartupServer.CommonLocalizer["YB0147"].Value);
                return;
            }
            try
            {
                var loginInfo = cached.Get<Domain.DtoModel.Sys.SysUserDto>(logId);
                if (loginInfo == null)
                {
                    // 认证Token信息错误或者您已经被强制退出登录
                    AuthFailSet(context, authorizationFailedContext, httpContext);
                    return;
                }
            }
            catch
            {
                // 请重新登录:登录信息版本不正确
                AuthFailSet(context, authorizationFailedContext, httpContext, StartupServer.CommonLocalizer["YB0147"].Value);
                return;
            }
            var operat = OperatorProvider.Get();
            var curRole = operat.RoleNameList;
            if (curRole.IndexOf("公司管理员") < 0 && curRole.IndexOf("测量管理员") < 0 && curRole.IndexOf("项目管理员") < 0 && curRole.IndexOf("测量员") < 0)
            {
                // 当前角色权限不足
                var msg = $"{StartupServer.CommonLocalizer["YB22040801"].Value}:{curRole};" + StartupServer.CommonLocalizer["YB0148"].Value;
                AuthFailSet(context, authorizationFailedContext, httpContext, msg);
                return;
            }

            if (requirement.RequirePolices.Contains("RequireCompanyManager") && curRole.IndexOf("公司管理员") < 0 && curRole.IndexOf("测量管理员") < 0)
            {
                // 当前角色权限不足
                string msg = $"{StartupServer.CommonLocalizer["YB22040801"].Value}:{curRole};" + StartupServer.CommonLocalizer["YB0148"].Value;
                AuthFailSet(context, authorizationFailedContext, httpContext, msg);
                return;
            }

            if (requirement.RequirePolices.Contains("RequireDeviceManager"))
            {
                string roleNameList = operat.RoleNameList;
                int userType = operat.UserType;
                int adminType = (int)EnumUserType.管理员;
                if (roleNameList.IndexOf("工厂管理员") == -1 || userType != adminType)
                {
                    // 需要用户角色为工厂管理员才能访问
                    AuthFailSet(context, authorizationFailedContext, httpContext, StartupServer.CommonLocalizer["YB0149"].Value);
                    return;
                }
            }

            if (!string.IsNullOrWhiteSpace(operat.WxOpenId))
            {
                var cachedOpenId = cached.Get<string>($"Applet:{operat.Id}:OpenId");
                if (!string.IsNullOrEmpty(cachedOpenId) && cachedOpenId != operat.WxOpenId)
                {
                    AuthFailSet(context, authorizationFailedContext, httpContext, "您的账号已被另一个微信用户登录，现已强制退出登录");
                    return;
                }
            }

            context.Succeed(requirement);

            //权限失败设置
            static void AuthFailSet(AuthorizationHandlerContext context, AuthorizationFailedContext authorizationFailedContext, HttpContext httpContext, string message = null)
            {
                context.Fail();
                if (!string.IsNullOrEmpty(message))
                    authorizationFailedContext.Message = message;
                httpContext.Features.Set(authorizationFailedContext);
            }
        }
    }
}